﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.Maps.MapControl;
using MyFloridaHouse.libSvBingMapsMultiShape;
using MyFloridaHouse.libSvOpenDistricts.DataService;
using MyFloridaHouse.libSvUtilities.CustomColors;
using MyFloridaHouse.ServiceConn.DistrBldrSvcRef;

namespace MyFloridaHouse.libSvOpenDistricts.MapShapes
{

    public partial class MapShapeLayer : INotifyPropertyChanged
    {
        #region property changed
        public event PropertyChangedEventHandler PropertyChanged;
        internal void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

        private Map myBingMap;
        private MapLayer myBingMapLayer;
        public double zoomLevel
        {
            get { return myBingMap.ZoomLevel; }
        }
        private ServiceDAL serviceDAL;

        private List<int> regionIDsInView = new List<int>();

        private List<Region> _layerRegionList;
        public List<Region> layerRegionList
        {
            get { return _layerRegionList; }
            set { _layerRegionList = value; }
        }


        public List<int> layerRegionIDList
        {
            get
            {
                List<int> newRregionIDList = new List<int>();
                foreach (var rgnID in layerRegionList)
                {
                    newRregionIDList.Add(rgnID.regionID);
                }
                return newRregionIDList;
            }
        }

        private double RegionIDsInViewZoomLevel = 0;

        private int _layerID;
        public int layerID { get { return _layerID; } }

        private string _layerName;
        public string layerName { get { return _layerName; } }

        private string _layerAbbr;
        public string layerAbbr { get { return _layerAbbr; } }

        private string _shapeType;
        public string shapeType { get { return _shapeType; } }

        private SolidColorBrush _lineStroke;
        public SolidColorBrush lineStroke { get { return _lineStroke; } }

        private bool _showFill;
        public bool showFill {
            get { return _showFill; }
            set
            {
                _showFill = value;
                ReplaceFillColor();
                NotifyPropertyChanged("showFill");
            }
         }

        private bool _getShapesRequestPending = false;
        public bool getShapesRequestPending
        {
            get { return _getShapesRequestPending; }
            set 
            { 
                _getShapesRequestPending = value; 
                NotifyPropertyChanged("getShapesRequestPending"); 
            }
        }

        private int _SelectedShapeIndex;
        public int SelectedShapeIndex
        {
            get { return _SelectedShapeIndex; }
            set { _SelectedShapeIndex = value; NotifyPropertyChanged("SelectedShapeIndex"); }
        }
        private string _layerColor;
        public string layerColor
        {
            get { return _layerColor; }
            set
            {
                _layerColor = value;
                _lineStroke = NamedColors.ConvertStringToColor(value);

                NotifyPropertyChanged("layerColor");
                NotifyPropertyChanged("lineStroke");
                NotifyPropertyChanged("SelectedColorIndex");

            }
        }


        private int _SelectedColorIndex;
        public int SelectedColorIndex
        {
            get { return _SelectedColorIndex; }
            set { _SelectedColorIndex = value; NotifyPropertyChanged("SelectedColorIndex"); }
        }

        private double _minZoomLevel;
        public double minZoomLevel
        {
            get { return _minZoomLevel; }
            set 
            { 
                _minZoomLevel = value;
                NotifyPropertyChanged("minZoomLevel");
                NotifyPropertyChanged("inZoomRange");
                ActivateLayer();
            }
        }

        private double _maxZoomLevel;
        public double maxZoomLevel
        {
            get { return _maxZoomLevel; }
            set 
            { 
                _maxZoomLevel = value;
                NotifyPropertyChanged("maxZoomLevel");
                NotifyPropertyChanged("inZoomRange");
                ActivateLayer();
            }
        }

        private bool _predefinedRegions;
        public bool predefinedRegions
        {
            get { return _predefinedRegions; }
            set { _predefinedRegions = value; NotifyPropertyChanged("predefinedRegions"); }
        }

        private bool _isConfigLayer;
        public bool isConfigLayer
        {
            get { return _isConfigLayer; }
            set { _isConfigLayer = value; NotifyPropertyChanged("isConfigLayer"); }
        }

        private bool _isBuildLayer;
        public bool isBuildLayer
        {
            get { return _isBuildLayer; }
            set { _isBuildLayer = value; NotifyPropertyChanged("isBuildLayer"); }
        }

        private bool _isDisplayLayer;
        public bool isDisplayLayer
        {
            get { return _isDisplayLayer; }
            set { _isDisplayLayer = value; NotifyPropertyChanged("isDisplayLayer"); }
        }


        private int _displayOrder;
        public int displayOrder
        {
            get { return _displayOrder; }
            set { _displayOrder = value; NotifyPropertyChanged("displayOrder"); }
        }


        public bool showTooltip { get; set; }

        private int _shapeCount;
        public int shapeCount
        {
            get { return _shapeCount; }
            set { _shapeCount = value; NotifyPropertyChanged("shapeCount"); }
        }

        private RegionDetails _regionDetails;
        public RegionDetails regionDetails
        {
            get { return _regionDetails; }
            set { _regionDetails = value; NotifyPropertyChanged("regionDetails"); }
        }

        private ObservableCollection<LayerDataMinMaxItem> _layerDataMinMaxList;
        public ObservableCollection<LayerDataMinMaxItem> layerDataMinMaxList
        {
            get { return _layerDataMinMaxList; }
            set { _layerDataMinMaxList = value; NotifyPropertyChanged("layerDataMinMaxList"); }
        }

        private bool _showLayer;
        public bool showLayer
        {
            get { return _showLayer; }
            set
            {
                if (value != _showLayer)
                {
                    _showLayer = value;
                    NotifyPropertyChanged("showLayer");
                    ActivateLayer();
                }
            }
        }

        private bool _showRegionNames;
        public bool showRegionNames
        {
            get { return _showRegionNames; }
            set
            {
                if (value != _showRegionNames)
                {
                    _showRegionNames = value;
                    NotifyPropertyChanged("showRegionNames");
                    ReplaceLabels();
                }
            }
        }


        private RegionLabelBase regionLabel;
        private RegionFillBase regionFill;

        // derived properties
        public bool inZoomRange 
        {
            get { return ((minZoomLevel <= myBingMap.ZoomLevel) & (myBingMap.ZoomLevel <= maxZoomLevel)); }
        }

        public bool isVisible
        {
            get
            {
                return (showLayer & inZoomRange);
            }
        }


        public enum enuMLayerAddLocation
        {
            top,
            bottom
        }

        public MapShapeLayer(ServiceDAL iServiceDAL, Map iMyMap, LayerDTO layerData, enuMLayerAddLocation addLocation, bool showTooltip, RegionLabelBase regionLabel, RegionFillBase regionFill)
        {
            serviceDAL = iServiceDAL;
            myBingMap = iMyMap;
            this.regionLabel = regionLabel;
            this.regionFill = regionFill;

            regionFill.RegionFillCriteriaChanged += onRegionFillCriteriaChanged;
            regionLabel.RegionLabelDataChanged += onRegionLabelDataChanged;

            myBingMapLayer = new MapLayer();
            if (addLocation == enuMLayerAddLocation.bottom)
            {
                // insert the data layer so it is on bottom of the layer list
                myBingMap.Children.Insert(0, myBingMapLayer);
            }
            else
            {
                // by default last one added is on top
                myBingMap.Children.Add(myBingMapLayer);
            }

            layerRegionList = new List<Region>();

            // non changeable constructor settings
            _layerID = layerData.layerid;
            _shapeType = layerData.layername;
            _layerName = layerData.layername;
            _layerAbbr = layerData.layerabbr;
            _isConfigLayer = layerData.isConfigLayer;
            _isBuildLayer = layerData.isBuildLayer; //jdm: 5/10
            _isDisplayLayer = layerData.isDisplayLayer;

            //_districtBuildIndex = layerData.districtBuildIndex;
            _displayOrder = layerData.displayOrder;

            //changeable settings
            showLayer = false;
            showRegionNames = false;
            showFill = false;
            layerColor = layerData.defaultstrokecolor;
            minZoomLevel = layerData.zoommin;
            maxZoomLevel = layerData.zoommax;

            // set defaults
            _SelectedShapeIndex = -1;

            this.showTooltip = showTooltip;

            // should never be used, but set just in case
            SetRegionDetails(1.0, new SolidColorBrush(Colors.Blue), 1.0, new SolidColorBrush(Colors.Orange));
        }


        internal void SetRegionDetails(double unselectedStrokeThickness, SolidColorBrush unselectedStrokeColor, double selectedStrokeThickness, SolidColorBrush selectedStrokeColor)
        {
            //no border for districts - mouse over or not
            regionDetails = new RegionDetails();
            regionDetails.selected = new RegionDetail();
            regionDetails.selected.stroke = selectedStrokeColor;
            regionDetails.selected.strokeThickness = selectedStrokeThickness;
            regionDetails.unselected = new RegionDetail();
            regionDetails.unselected.stroke = unselectedStrokeColor;
            regionDetails.unselected.strokeThickness = unselectedStrokeThickness;
        }

        public void HideLayer()
        {
            foreach (var region in layerRegionList)
            {

                region.Hide();
            }
        }

        public void ShowLayer()
        {
            if (showLayer)
            {
                if (inZoomRange)
                {
                    foreach (var region in layerRegionList)
                    {
                        region.Show();
                    }
                }
                else
                {
                    HideLayer();
                }
            }
        }

        public void ActivateLayer()
        {
            if (_showLayer)
            {
                if (inZoomRange)
                {
                    ShowLayer();

                    if (predefinedRegions)
                    {
                        // need to get any shapes that weren't already retrieved
                        getShapesRequestPending = true;
                        serviceDAL.GetRegionIDsInView(layerID, myBingMap.BoundingRectangle);
                    }
                }
                else
                {
                    HideLayer();
                }
            }
            else
            {
                HideLayer();
            }
        }

        public void ReplaceLabels()
        {
            foreach (var region in layerRegionList)
            {
                region.ReplaceLabelShape();
            }
        }

        public void ReplaceFillColor()
        {
            foreach (var rgn in layerRegionList)
            {
                rgn.ReplaceFillColor();
            }
        }

        public bool IsRegionInView(BoundingBoxDTO regionBoundingBox)
        {
            //, LocationRect locationRect, BoundingBoxDTO regionBoundingBoxDTO)

            if ((regionBoundingBox == null)
                | (regionBoundingBox.northWest == null)
                | (regionBoundingBox.southEast == null)
            )
            {
                return false;
            }

            if ((regionBoundingBox.southEast.latitude > myBingMap.BoundingRectangle.North)
                | (regionBoundingBox.northWest.latitude < myBingMap.BoundingRectangle.South)
                | (regionBoundingBox.northWest.longitude > myBingMap.BoundingRectangle.East)
                | (regionBoundingBox.southEast.longitude < myBingMap.BoundingRectangle.West)
                )
            {
                return false;
            }
            return true ;
        }


        public void onRegionFillCriteriaChanged(object sender, EventArgs args)
        {
            RefreshDataInView();
        }

        public void onRegionLabelDataChanged(object sender, EventArgs args)
        {
            RefreshDataInView();
        }

        public void RefreshDataInView()
        {
            if (isVisible)
            {
                if (predefinedRegions)
                {
                    serviceDAL.GetRegionListData(layerID, layerRegionIDList);
                }
            }
        }

        public List<int> SaveRegionsInView(List<int> regionIDsInViewList)
        {

            List<int> regionIDsOutOfView = new List<int>();

            if (RegionIDsInViewZoomLevel != myBingMap.ZoomLevel)
            {
                // new zoom level means we need to get new shapes for everything visible, so all currently loaded regions are "out of view"
                regionIDsOutOfView = layerRegionIDList;
            }
            else
            {
                regionIDsOutOfView = layerRegionIDList.Except(regionIDsInViewList).ToList();
            }

            // We need to know all regions we are going to load
            regionIDsInView = regionIDsInViewList;
            RegionIDsInViewZoomLevel = myBingMap.ZoomLevel;

            // Determine "out of view" regions, delete them from existence
            RemoveRegions(regionIDsOutOfView);
            
            // determine IDs without shape data
            List<int> newRegionsIDsWithoutShapes = regionIDsInView.Except(layerRegionIDList).ToList();

            return newRegionsIDsWithoutShapes;

        }


        public void ClearRegions()
        {
            RemoveRegions(layerRegionIDList);
        }


        private void RemoveRegions(List<int> regionIDsToRemove)
        {
            foreach (var rgnID in regionIDsToRemove)
            {
                RemoveRegion(rgnID);
            }
        }

        internal void RemoveRegion(int regionIDToRemove)
        {
            var rgnList = (from r in layerRegionList
                           where r.regionID == regionIDToRemove
                           select r);

            List<Region> removeList = new List<Region>();
            foreach (var rgn in rgnList)
            {
                removeList.Add(rgn);
            }
            foreach(var rgn in removeList)
            {
                layerRegionList.Remove(rgn);
                rgn.Dispose();
            }
        }


        public void LoadRegions(List<MapRegionDTO> regionList, List<RegionDetails> regionDetailList, RegionLabelBase regionLabel)
        {
            //protect against different sized lists
            if (regionDetailList != null)
            {
                if (regionList.Count != regionDetailList.Count)
                {
                    //todo: Handle this exception in a gracefull manner
                    throw new Exception("regionList and regionDetailList don't match in LoadRegions");
                }
            }

            // now load all the new shapes
            for (int rgnIndex = 0; rgnIndex < regionList.Count; rgnIndex++)
            {

                MapRegionDTO rgnShape = regionList[rgnIndex];

                // can't access the constructor until we reference libSvDTO directly and not through the service
                BoundingBoxDTO regionBoundingBox = new BoundingBoxDTO();
                regionBoundingBox.northWest = new PointDTO();
                regionBoundingBox.northWest.latitude = rgnShape.northWestLat;
                regionBoundingBox.northWest.longitude = rgnShape.northWestLng;
                regionBoundingBox.southEast = new PointDTO();
                regionBoundingBox.southEast.latitude = rgnShape.northWestLat;
                regionBoundingBox.southEast.longitude = rgnShape.northWestLng;


                // create a new region
                Region newRegion = new Region(rgnShape.regionID, rgnShape.name,  myBingMapLayer, new Location(rgnShape.centerLatY, rgnShape.centerLngX), regionBoundingBox, regionLabel, regionFill);
                newRegion.layer = this;
                newRegion.dataList = new List<RegionDataDTO>();

                // are there custom details for each region?
                if (regionDetailList == null)
                {
                    // no specific region details, use the layer default
                    newRegion.details = regionDetails;
                }
                else
                {
                    // there are custom details provided, use them
                    newRegion.details = regionDetailList[rgnIndex];
                }

                // add the new region  to the layer
                AddMapShapeToRegion(newRegion, rgnShape.mapMultiShape, regionBoundingBox, showTooltip);
                layerRegionList.Add(newRegion);

            }

            ShowLayer();

        }

        private static void AddMapShapeToRegion( Region region, MapMultiShapeBase mapShape,BoundingBoxDTO boundingBox, bool showTooltip)
        {
            if (mapShape != null)
            {
                // add shape features
                mapShape.Tag = region;
                mapShape.Stroke = region.details.unselected.stroke;
                mapShape.StrokeThickness = region.details.unselected.strokeThickness;
                Region.AddShapeEventHandlers(mapShape);
                region.AddShape(mapShape);
                region.boundingBox = boundingBox;
                if (showTooltip)
                {
                    AddToolTip(region.regionShape, region.regionID.ToString());
                }
            }
        }


        public static void AddToolTip(MapMultiShapeBase shp, string ttText)
        {
            if (shp != null)
            {
                // Add Tooltip 
                var tooltipObject = new StackPanel();

                var title = new TextBlock();
                title.FontWeight = FontWeights.Bold;
                title.Text = ttText;
                tooltipObject.Children.Add(title);

                ToolTipService.SetToolTip(shp, tooltipObject);
            }
        }

        

        //private void TurnPointIntoIcon()
        //{
        //     turn the point into a polygon
        //    MapMultiPolygon mapPoly = new MapMultiPolygon();
        //    mapPoly.Vertices = new List<LocationCollection>();
        //    // todo: Modify point BuildIcon for custom icons instead of a fixed square
        //    SharpMap.Geometries.Point smPoint = (SharpMap.Geometries.Point)gm;
        //    mapPoly.Vertices.Add(BuildIcon(myBingMap, new Location(smPoint.Y, smPoint.X)));
        //    mapShape = mapPoly;
        //}

        //private static LocationCollection BuildIcon(Map myBingMap, Location loc)
        //{
        //    // Build Point inside box that is approximately (2*side) meters per side
        //    double side = 10;
        //    double mpp = MercatorUtility.ZoomToScale(new Size(512, 512), myBingMap.ZoomLevel, loc);
        //    double offset = side / mpp;

        //    System.Windows.Point pt = myBingMap.LocationToViewportPoint(loc);

        //    pt.X -= offset;
        //    pt.Y -= offset;
        //    Location nw = myBingMap.ViewportPointToLocation(pt);

        //    pt.X += (2 * offset);
        //    pt.Y += (2 * offset);
        //    Location se = myBingMap.ViewportPointToLocation(pt);

        //    LocationRect rect = new LocationRect(nw,se);

        //    LocationCollection polygonLocation = new LocationCollection();
        //    polygonLocation.Add(rect.Northeast);
        //    polygonLocation.Add(rect.Northwest);
        //    polygonLocation.Add(rect.Southwest);
        //    polygonLocation.Add(rect.Southeast);
        //    polygonLocation.Add(rect.Northeast);
        //    return polygonLocation;
        //}
    

        public void LoadRegionsData(ObservableCollection<RegionDataDTO> regionsDataList)
        {
            // set up the fill control so it knows about the data max/mins on the layer so it can do a fill if asked
            if (regionsDataList != null)
            {
                // set up the fill control so it knows about the data max/mins on the layer so it can do a fill if asked
                regionFill.AggregateLayerData(layerID, regionsDataList);
            }


            // now load all the new shapes
            foreach (Region currentRegion in layerRegionList)
            {
                if (regionsDataList != null)
                {
                    // find the data for this region and store it in the region data object
                    // need to check for null here because new district won't have data yet
                    List<RegionDataDTO> regionDataList = (from rgn in regionsDataList
                                                          where currentRegion.regionID == rgn.regionID
                                                          select rgn).ToList();
                    // only load data for regions retrieved.  Pre-existing regions need to be left alone
                    if (regionDataList.Count() > 0)
                    {
                        currentRegion.ReplaceData(regionDataList);
                    }

                }
            }

        }
    }

}
